import { DefaultOptons, Options, TrackerConfig } from "../types/index";
import { createHistoryEvent } from "../utils/pv";

const MouseEventList: string[] = ['click', 'dblclick', 'contextmenu', 'mousedown', 'mouseup', 'mouseenter', 'mouseout', 'mouseover']

export default class Tracker {
    public data: DefaultOptons & Options;
    constructor(options: Options) {
        this.data = Object.assign(this.initDef(), options)

        // 重新history方法
        window.history['pushState'] = createHistoryEvent('pushState')
        window.history['replaceState'] = createHistoryEvent('replaceState')

        this.installEvent()
    }

    private initDef (): DefaultOptons {
        // 断言
        return <DefaultOptons>{
            historyTracker: false,
            hashTracker: false,
            domTracker: false,
            sdkVersion: TrackerConfig.version,
            jsError: false,
            historyPvName: 'history-pv',
            hashPvName: 'hash-pv',
            domTargetAttr: 'target-key'
        }
    }

    // pv
    private captureEvents <T>(eventList: string[], targetKey: string, data?: T) {
        eventList.forEach(event => {
            window.addEventListener(event, () => {
                // 监听到浏览器路由变化,发送上报请求
                this.reportTracker({
                    event,
                    targetKey,
                    data
                })
            })
        })
    }

    private installEvent () {
        const { historyPvName, hashPvName } = this.data
        if (this.data.historyTracker) {
            this.captureEvents(['pushState', 'replaceState', 'popState'], historyPvName)
        }
        if (this.data.historyTracker) {
            this.captureEvents(['hashChange'], hashPvName)
        }
        if (this.data.domTracker) {
            this.targetKeyReport()
        }
        if (this.data.jsError) {
            this.errorEvent()
            this.promiseReject()
        }
    }

    // 开放给用户手动上报的方法
    public sendTracker<T>(data: T) {
        this.reportTracker(data)
    }

    // uv
    // 登录之后可以设置userId
    public setUserId <T extends DefaultOptons['uuid']>(uuid: T) {
        this.data.uuid = uuid
    }

    // 用户可以自定义参数
    public setExtra <T extends DefaultOptons['extra']>(extra: T) {
        this.data.extra = extra
    }

    // 接口上报
    private reportTracker<T>(data: T) {
        if (!this.data.requestUrl) return
        const params = Object.assign(this.data, data, { time: new Date().getTime() })
        const blob = new Blob([JSON.stringify(params)], {type: 'application/x-www-form-urlencoded'})
        // sendBeacon在关闭页面的时候,还是会把未完成的请求发出,不会丢失,很适合做埋点的请求上报
        // 只支持post方法,不能使用get方法,传输类型是ping,只能传输少量数据64kb
        // 需要传输json的话可以把数据转成blob上传
        navigator.sendBeacon(this.data.requestUrl, blob)
    }

    // 按钮事件上报
    private targetKeyReport() {
        const { domTargetAttr } = this.data
        MouseEventList.forEach(evt => {
            window.addEventListener(evt, (e) => {
                const target = e.target as HTMLElement
                const targetKey = target.getAttribute(domTargetAttr)
                if (targetKey) {
                    this.reportTracker({
                        event: evt,
                        targetKey
                    })
                }
            })
        })
    }

    // error上报,
    // js报错,使用window.addEventListener('error')
    private errorEvent() {
        window.addEventListener('error', (event) => {
            this.reportTracker({
                event: 'error',
                targetKey: 'message',
                message: event.message
            })
        })
    }

    // promiseReject报错,使用window.addEventListener('unhandledrejection')
    private promiseReject () {
        window.addEventListener('unhandledrejection', (event) => {
            event.promise.catch(error => {
                this.reportTracker({
                    event: 'promiseReject',
                    targetKey: 'message',
                    message: error
                })
            })
        })
    }
}